﻿#region COPYRIGHT
//--------------------------------------------------------------------------------
// <copyright file="Emitter.cs" company="starLiGHT Entertainment Studios">
//    Copyright (c) 2008, 2009, 2010, 2011
//       Roland Rosenkranz (Glatzemann@email.de)
// </copyright>
// <license>
//   This file is part of starLiGHT.Particles.
//
//   starLiGHT.Particles is free software: you can redistribute it and/or modify
//   it under the terms of the GNU Lesser General Public License as published by
//   the Free Software Foundation, either version 3 of the License, or
//   (at your option) any later version.
//
//   starLiGHT.Particles is distributed in the hope that it will be useful,
//   but WITHOUT ANY WARRANTY; without even the implied warranty of
//   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//   GNU Lesser General Public License for more details.
//
//   You should have received a copy of the GNU Lesser General Public License
//   along with starLiGHT.Particles. If not, see http://www.gnu.org/licenses/.
//    
//   ADDITIONAL (commercial) LICENSES for starLiGHT.Particles are available on request.
// </license>
// <original_license>
//  * FLINT PARTICLE SYSTEM
//  * .....................
//  * 
//  * Author: Richard Lord
//  * Copyright (c) Richard Lord 2008-2009
//  * http://flintparticles.org
//  * 
//  * 
//  * Licence Agreement
//  * 
//  * Permission is hereby granted, free of charge, to any person obtaining a copy
//  * of this software and associated documentation files (the "Software"), to deal
//  * in the Software without restriction, including without limitation the rights
//  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//  * copies of the Software, and to permit persons to whom the Software is
//  * furnished to do so, subject to the following conditions:
//  * 
//  * The above copyright notice and this permission notice shall be included in
//  * all copies or substantial portions of the Software.
//  * 
//  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//  * THE SOFTWARE.
// </original_license>
// <version>
// **************[ starLiGHT.Engine SVN ]**********************
// * $Rev:: 620                   $:  Revision of last commit *
// * $Author:: unknown            $:  Author of last commit   *
// * $Date:: 2010-12-07 15:23:58 #$:  Date of last commit     *
// ************************************************************
// </version>
//--------------------------------------------------------------------------------
#endregion

namespace starLiGHT.ParticleSystem.Common
{
    #region Using Statements
    using System;
    using System.Collections.Generic;
    using System.Text;
    using Microsoft.Xna.Framework;
    using starLiGHT.ParticleSystem.Initializer;
    using starLiGHT.ParticleSystem.Modifier;
    using starLiGHT.ParticleSystem.PulseGenerators;
    using starLiGHT.SceneManagement;

    #endregion

    public class ParticleEventArgs : EventArgs
    {
        public ParticleEventArgs(Particle p)
        {
            this.Particle = p;
        }

        public Particle Particle
        {
            get;
            set;
        }
    }

    public abstract class Emitter
    {
        #region Events

        public delegate void ParticleEventHandler(object sender, ParticleEventArgs e);

        public event ParticleEventHandler ParticleDead;

        public event ParticleEventHandler ParticleCreated;
        
        public event ParticleEventHandler ParticleAdded;

        public event EventHandler EmitterEmpty;

        public event EventHandler EmitterUpdated;

        public event EventHandler PulseGeneratorComplete;

        #endregion

        #region Member Variables
        protected IParticleFactory _particleFactory;
        private List<Initializer> _initializers;
        private List<IModifier> _modifiers;
        private List<IActivity> _activities;
        private List<Particle> _particles;
        private IPulseGenerator _pulseGenerator;
        private bool _useInternalTick = true;
        private float _fixedFrameTime;
        private bool _running = false;
        private bool _started = false;
        private float _maximumFrameTime = 0.1f;
        private bool _dispatchCounterComplete = false;
        private System.Random random;
        private NameGenerator mEmitterNameGenerator = new NameGenerator("Emitter");

        #endregion

        public Emitter()
        {
            this._particles = new List<Particle>();
            this._modifiers = new List<IModifier>();
            this._initializers = new List<Initializer>();
            this._activities = new List<IActivity>();
            this._pulseGenerator = new NullPulse();
            this.random = new System.Random();
            this.Name = mEmitterNameGenerator.Generate();
        }

        public Emitter(string name)
        {
            this._particles = new List<Particle>();
            this._modifiers = new List<IModifier>();
            this._initializers = new List<Initializer>();
            this._activities = new List<IActivity>();
            this._pulseGenerator = new NullPulse();
            this.random = new System.Random();
            this.Name = name;
        }

        public System.Random Random
        {
            get { return random; }
        }

        public float MaximumFrameTime
        {
            get { return _maximumFrameTime; }
            set { _maximumFrameTime = value; }
        }

        public List<Initializer> Initializers
        {
            get 
            { 
                return _initializers; 
            }

            set
            {
                foreach (Initializer initializer in _initializers)
                {
                    initializer.removedFromEmitter(this);
                }

                _initializers.Clear();
                ////////TODO: BehaviourArrayUtils.sortArray(_initializers);
                foreach (Initializer initializer in value)
                {
                    initializer.addedToEmitter(this);
                }
            }
        }

        public void addInitializer(Initializer initializer)
        {
            _initializers.Add(initializer);
            initializer.addedToEmitter(this);
        }

        public void removeInitializer(Initializer initializer)
        {
            _initializers.Remove(initializer);
            initializer.removedFromEmitter(this);
        }

        public bool hasInitializer(Initializer initializer)
        {
            return _initializers.Contains(initializer);
        }

        public bool hasInitializerOfType(Type type)
        {
            foreach (var i in _initializers)
            {
                if (i.GetType().Equals(type))
                    return true;
            }

            return false;
        }

        public List<IModifier> Modifiers
        {
            get 
            { 
                return _modifiers; 
            }

            set
            {
                foreach (IModifier modifier in _modifiers)
                {
                    modifier.removedFromEmitter(this);
                }

                _modifiers.Clear();
                ////////TODO: BehaviourArrayUtils.sortArray(_modifiers);
                foreach (IModifier modifier in value)
                {
                    modifier.addedToEmitter(this);
                }
            }
        }

        public void addModifier(IModifier modifier)
        {
            ////////TODO: BehaviourArrayUtils.add(_actions, action);
            _modifiers.Add(modifier);
            modifier.addedToEmitter(this);
        }

        public void removeModifier(IModifier modifier)
        {
            _modifiers.Remove(modifier);
            modifier.removedFromEmitter(this);
        }

        public bool hasModifier(IModifier modifier)
        {
            return _modifiers.Contains(modifier);
        }

        public bool hasModifierOfType(Type type)
        {
            foreach (var m in _modifiers)
            {
                if (m.GetType().Equals(type))
                    return true;
            }

            return false;
        }

        public List<IActivity> Activities
        {
            get 
            { 
                return this._activities; 
            }

            set
            {
                foreach (IActivity activity in this._activities)
                {
                    activity.removedFromEmitter(this);
                }

                this._activities.Clear();
                // TODO: BehaviourArrayUtils.sortArray(this._activities);
                foreach (IActivity activity in value)
                {
                    activity.addedToEmitter(this);
                }
            }
        }

        public void addActivity(IActivity activity)
        {
            this._activities.Add(activity);
            activity.addedToEmitter(this);
        }

        public void removeActivity(IActivity activity)
        {
            this._activities.Remove(activity);
            activity.removedFromEmitter(this);
        }

        public bool hasActivity(IActivity activity)
        {
            return this._activities.Contains(activity);
        }

        public bool hasActivityOfType(Type type)
        {
            foreach (var a in this._activities)
            {
                if (a.GetType().Equals(type))
                    return true;
            }

            return false;
        }

        /// <summary>
        /// The Counter for the Emitter. The counter defines when and
        /// with what frequency the emitter emits particles.
        /// </summary>
        public IPulseGenerator PulseGenerator
        {
            get 
            { 
                return _pulseGenerator; 
            }

            set
            {
                _pulseGenerator = value;
                if (_running)
                {
                    _pulseGenerator.startEmitter(this);
                }
            }
        }

        public void dispatchPulseGeneratorComplete()
        {
            this._dispatchCounterComplete = true;
        }

        public bool Running
        {
            get { return _running; }
        }

        public IParticleFactory ParticleFactory
        {
            get { return _particleFactory; }
            set { _particleFactory = value; }
        }

        public List<Particle> Particles
        {
            get 
            { 
                return _particles; 
            }

            set 
            {
                killAllParticles();
                addExistingParticles(value, false);
            }
        }

        protected Particle createParticle()
        {
            Particle particle = _particleFactory.createParticle();
            int len = _initializers.Count;
            initParticle(particle);
            for (int i = 0; i < len; ++i)
            {
                _initializers[i].initialize(this, ref particle);
            }

            _particles.Add(particle);
            if (ParticleCreated != null) ParticleCreated(this, new ParticleEventArgs(particle));
            return particle;
        }

        protected virtual void initParticle(Particle particle)
        {
        }

        public void addExistingParticles(List<Particle> particles) { addExistingParticles(particles, false); }

        public void addExistingParticles(List<Particle> particles, bool applyInitializers)
        {
            int len = particles.Count;
            int i;
            if (applyInitializers)
            {
                int len2 = _initializers.Count;
                for (int j = 0; j < len2; ++j)
                {
                    for (i = 0; i < len; ++i)
                    {
                        Particle p = particles[i];
                        _initializers[j].initialize(this, ref p);
                        particles[i] = p;
                    }
                }
            }

            for (i = 0; i < len; ++i)
            {
                _particles.Add(particles[i]);
                if (ParticleAdded != null) ParticleAdded(this, new ParticleEventArgs(particles[i]));
            }
        }

        public void killAllParticles()
        {
            int len = _particles.Count;
            for (int i = 0; i < len; ++i)
            {
                if (ParticleDead != null) ParticleDead(this, new ParticleEventArgs(_particles[i]));
                _particleFactory.disposeParticle(_particles[i]);
            }
        }

        public void start()
        {
            if (_useInternalTick)
            {
                ////////TODO: add event listener
            }

            _started = true;
            _running = true;
            int len = this._activities.Count;
            for (int i = 0; i < len; ++i)
            {
                this._activities[i].initialize(this);
            }

            uint len2 = _pulseGenerator.startEmitter(this);
            for (uint i = 0; i < len2; ++i)
            {
                createParticle();
            }
        }

        public void updateEventListener(GameTime gameTime)
        {
            if (_fixedFrameTime > 0)
            {
                update(_fixedFrameTime);
            }
            else
            {
                update((float)gameTime.ElapsedGameTime.TotalSeconds);
            }
        }

        public void update(float time)
        {
            if (!_running) return;
            if (time > _maximumFrameTime) time = _maximumFrameTime;

            int i;
            Particle particle;
            uint len = _pulseGenerator.updateEmitter(this, time);
            for (i = 0; i < len; ++i)
            {
                createParticle();
            }

            sortParticles();
            len = (uint)this._activities.Count;
            for (i = 0; i < len; ++i)
            {
                this._activities[i].update(this, time);
            }

            if (_particles.Count > 0)
            {
                // update particle state
                len = (uint)_modifiers.Count;
                IModifier modifier;
                int len2 = _particles.Count;

                for (int j = 0; j < len; ++j)
                {
                    modifier = _modifiers[j];
                    for (i = 0; i < len2; ++i)
                    {
                        particle = _particles[i];
                        modifier.Update(this, particle, time);
                    }
                }

                // remove dead particles
                for (i = len2 - 1; i >= 0; i--)
                {
                    particle = _particles[i];
                    if (particle.isDead)
                    {
                        _particles.RemoveAt(i);
                        if (ParticleDead != null)
                        {
                            ParticleDead(this, new ParticleEventArgs(particle));
                        }

                        if (particle.isDead)
                        {
                            _particleFactory.disposeParticle(particle);
                        }
                    }
                }
            }
            else
            {
                if (EmitterEmpty != null) EmitterEmpty(this, new EventArgs());
            }

            if (EmitterUpdated != null)
            {
                EmitterUpdated(this, new EventArgs());
            }

            if (this._dispatchCounterComplete)
            {
                this._dispatchCounterComplete = false;
                if (PulseGeneratorComplete != null)
                {
                    PulseGeneratorComplete(this, new EventArgs());
                }
            }
        }

        /// <summary>
        /// Used to sort the particles as required. In this base class this method
        /// does nothing.
        /// </summary>
        protected virtual void sortParticles()
        {
        }

        public void pause()
        {
            _running = false;
        }

        public void resume()
        {
            _running = true;
        }

        public void stop()
        {
            if (_useInternalTick)
            {
                ////////TODO: remove event listener Update
            }

            _started = false;
            _running = false;
            killAllParticles();
        }

        public void runAhead(float time) { runAhead(time, 10f); }

        public void runAhead(float time, float frameRate)
        {
            float maxTime = _maximumFrameTime;
            float step = 1f / frameRate;
            _maximumFrameTime = step;
            while (time > 0)
            {
                time -= step;
                update(step);
            }

            _maximumFrameTime = maxTime;
        }

        public string Name
        {
            get;
            set;
        }
    }
}
